#if !defined(_WIN32)

#include	<Network.h>

#include	<arpa/inet.h>
#include	<fcntl.h>
#include	<netinet/in.h>
#include	<sys/epoll.h>
#include	<sys/socket.h>
#include	<sys/types.h>
#include	<unistd.h>
#include	<cstring>

//////////////////////////////////////////////////////////////////////////
/// Unix Socket wrapper.
//////////////////////////////////////////////////////////////////////////
class Socket : public ISocket {
public:
	Socket();
	virtual ~Socket();

	virtual int			Connect(const std::string & sIP, int nPort) override;
	virtual int			Listen(const std::string & sIP, int nPort) override;
	virtual ISocket *	Accept(uint32_t & nIP) override;
	virtual int			Recv(char * pBuf, size_t nSize) override;
	virtual bool		Send(const char * pData, size_t nSize) override;
	virtual void		Close(int nReason) override;

private:
	int _nSocket;
};

//////////////////////////////////////////////////////////////////////////
/// IMPLEMENT Global network interfaces.
//////////////////////////////////////////////////////////////////////////
namespace Network {

	static int GEpollFD = -1;

	int GetEpoll() {
		if (GEpollFD == -1) {
			GEpollFD = epoll_create(1);
			if (GEpollFD < 0) throw std::runtime_error("epoll_create for Network failed");
			atexit([=]() { close(GEpollFD); });
		}

		return GEpollFD;
	}

	ISocket * CreatePlatformSocket() {
		return new Socket();
	}

	void Register(int nSocket, Socket * pListener) {
		epoll_event iEv;
		iEv.events = EPOLLIN;
		iEv.data.ptr = pListener;
		epoll_ctl(GetEpoll(), EPOLL_CTL_ADD, nSocket, &iEv);
	}

	void Unregister(int nSocket) {
		epoll_ctl(GetEpoll(), EPOLL_CTL_DEL, nSocket, NULL);
	}

	void Breath() {
		static epoll_event pEvents[4096] = { 0 };
		
		int nCount = epoll_wait(GetEpoll(), pEvents, 4096, 0);
		if (nCount <= 0) return;

		for (int i = 0; i < nCount; ++i) {
			if (!(pEvents[i].events & EPOLLIN)) continue;
			Socket * pSocket = (Socket *)pEvents[i].data.ptr;
			if (pSocket && pSocket->pListener) pSocket->pListener->OnProcess(NET_EVENT_READABLE, 0);
		}
	}
}

//////////////////////////////////////////////////////////////////////////
/// IMPLEMENT Win32Socket
//////////////////////////////////////////////////////////////////////////

Socket::Socket() : _nSocket(-1) {
}

Socket::~Socket() {
	Close(NET_CLOSE_LOCAL);
}

int Socket::Connect(const std::string & sIP, int nPort) {
	if (_nSocket >= 0) return NET_ERR_RUNNING;

	struct sockaddr_in iAddr;
	memset(&iAddr, 0, sizeof(iAddr));

	iAddr.sin_family = AF_INET;
	iAddr.sin_port = htons(nPort);

	if (0 >= inet_pton(AF_INET, sIP.c_str(), &iAddr.sin_addr)) return NET_ERR_ADDR;

	_nSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (_nSocket < 0) return errno;

	int nFlags = fcntl(_nSocket, F_GETFL, 0);
	if (fcntl(_nSocket, F_SETFL, O_NONBLOCK | nFlags) < 0) {
		close(_nSocket);
		_nSocket = -1;
		return errno;
	}

	if (connect(_nSocket, (sockaddr *)&iAddr, sizeof(sockaddr)) < 0) {
		if (errno != EINPROGRESS) {
			close(_nSocket);
			_nSocket = -1;
			return errno;
		}

		fd_set iSet;
		struct timeval iWait;

		iWait.tv_sec = 1;
		iWait.tv_usec = 0;

		FD_ZERO(&iSet);
		FD_SET(_nSocket, &iSet);

		if (select(_nSocket + 1, 0, &iSet, 0, &iWait) <= 0) {
			close(_nSocket);
			_nSocket = -1;
			return NET_ERR_TIMEOUT;
		}

		int nErr;
		socklen_t nLen;
		if (getsockopt(_nSocket, SOL_SOCKET, SO_ERROR, &nErr, &nLen) < 0 || nErr != 0) {
			close(_nSocket);
			_nSocket = -1;
			return NET_ERR_CLOSED;
		}
	}

	Network::Register(_nSocket, this);
	if (pListener) pListener->OnProcess(NET_EVENT_CONNECTED, 0);
	return 0;
}

int Socket::Listen(const std::string & sIP, int nPort) {
	if (_nSocket >= 0) return NET_ERR_RUNNING;

	struct sockaddr_in iAddr;
	memset(&iAddr, 0, sizeof(iAddr));

	iAddr.sin_family = AF_INET;
	iAddr.sin_port = htons(nPort);

	if (0 >= inet_pton(AF_INET, sIP.c_str(), &iAddr.sin_addr)) return NET_ERR_ADDR;

	_nSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (_nSocket < 0) return errno;

	int nFlags = fcntl(_nSocket, F_GETFL, 0);
	if (fcntl(_nSocket, F_SETFL, O_NONBLOCK | nFlags) < 0) {
		close(_nSocket);
		_nSocket = -1;
		return errno;
	}

	int nReuse = 1;
	setsockopt(_nSocket, SOL_SOCKET, SO_REUSEADDR, &nReuse, sizeof(nReuse));

	if (::bind(_nSocket, (sockaddr *)&iAddr, sizeof(sockaddr)) < 0 || ::listen(_nSocket, 1024) < 0) {
		close(_nSocket);
		_nSocket = -1;
		return errno;
	}

	Network::Register(_nSocket, this);
	return 0;
}

ISocket * Socket::Accept(uint32_t & nIP) {
	sockaddr_in iAddr = { 0 };
	socklen_t nSizeOfAddr = sizeof(iAddr);
	int nAccept = accept(_nSocket, (sockaddr *)&iAddr, &nSizeOfAddr);
	if (nAccept < 0) return nullptr;

	int nFlags = fcntl(_nSocket, F_GETFL, 0);
	if (fcntl(_nSocket, F_SETFL, O_NONBLOCK | nFlags) < 0) {
		close(_nSocket);
		_nSocket = -1;
		return nullptr;
	}

	Socket * pAccept = new Socket;
	pAccept->_nSocket = nAccept;
	Network::Register(nAccept, pAccept);

	nIP = iAddr.sin_addr.s_addr;
	return pAccept;
}

int Socket::Recv(char * pBuf, size_t nSize) {
	if (_nSocket < 0) return NET_RECV_CLOSED;

	int nRecv = (int)recv(_nSocket, pBuf, (int)nSize, MSG_DONTWAIT);
	if (nRecv > 0) return nRecv;
	if (nRecv < 0 && errno == EAGAIN) return NET_RECV_PENDING;
	return nRecv == 0 ? NET_RECV_CLOSED : NET_RECV_ERROR;
}

bool Socket::Send(const char * pData, size_t nSize) {
	if (_nSocket < 0) return false;

	char *	pSend = (char *)pData;
	int		nSend = 0;
	int		nLeft = (int)nSize;

	while (true) {
		nSend = (int)send(_nSocket, pSend, nLeft, MSG_DONTWAIT);
		if (nSend < 0) {
			if (errno == EAGAIN) {
				usleep(1000);
			} else {
				return false;
			}
		} else if (nSend < nLeft) {
			nLeft -= nSend;
			pSend += nSend;
		} else if (nSend == nLeft) {
			return true;
		} else {
			return nLeft == 0;
		}
	}
}

void Socket::Close(int nReason) {
	if (_nSocket < 0) return;
	Network::Unregister(_nSocket);
	close(_nSocket);
	_nSocket = -1;
	if (pListener) pListener->OnProcess(NET_EVENT_CLOSED, nReason);
}

#endif